package com.jse.api;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
 * 用于生成阿里云短信服务API请求的工具类。
 * 
 * 此类提供了一个静态方法 `api` 用于构建短信发送、批量短信发送及查询发送记录的API请求URL。
 * 
 * @author dzh
 */
public class Sms {

	/**
	 * 生成阿里云短信服务API请求的URL。
	 * 
	 * @param paras 请求参数，必须包含：
	 *             <ul>
	 *                 <li>AccessKeyId (String): 用户的Access Key ID。</li>
	 *                 <li>AccessSecret (String): 用户的Access Secret。</li>
	 *                 <li>Action (String): 操作类型，如 "SendSms", "SendBatchSms", 或 "QuerySendDetails"。</li>
	 *                 <li>以及其他根据操作类型所需的参数：</li>
	 *                 <ul>
	 *                     <li>如果 Action 为 "SendSms"，则还需要包含：
	 *                         <ul>
	 *                             <li>PhoneNumbers (String): 接收短信的手机号码。</li>
	 *                             <li>SignName (String): 短信签名。</li>
	 *                             <li>TemplateParam (String): 模板参数JSON格式。</li>
	 *                             <li>TemplateCode (String): 短信模板编号。</li>
	 *                             <li>OutId (String, 可选): 外部流水扩展字段。</li>
	 *                         </ul>
	 *                     </li>
	 *                     <li>如果 Action 为 "SendBatchSms"，则还需要包含：
	 *                         <ul>
	 *                             <li>PhoneNumberJson (String): 批量发送短信的手机号码JSON数组。</li>
	 *                             <li>SignNameJson (String, 可选): 短信签名JSON数组。</li>
	 *                             <li>TemplateParamJson (String, 可选): 模板参数JSON数组。</li>
	 *                             <li>TemplateCode (String): 短信模板编号。</li>
	 *                             <li>SmsUpExtendCodeJson (String, 可选): 上行短信扩展码JSON数组。</li>
	 *                         </ul>
	 *                     </li>
	 *                     <li>如果 Action 为 "QuerySendDetails"，则还需要包含：
	 *                         <ul>
	 *                             <li>SendDate (String): 发送日期，格式为yyyyMMdd。</li>
	 *                             <li>PhoneNumber (String): 用于查询的手机号码。</li>
	 *                             <li>CurrentPage (String): 当前页数。</li>
	 *                             <li>PageSize (String): 每页大小。</li>
	 *                             <li>BizId (String, 可选): 业务流水ID，用于精确查询。</li>
	 *                         </ul>
	 *                     </li>
	 *                 </ul>
	 * @return 生成的API请求URL。
	 */
    public static String api(Map<String, String> paras) {
        String accessSecret = paras.remove("AccessSecret");
        String action = "http://dysmsapi.aliyuncs.com/?Signature=";

        SortedMap<String, String> sortedParas = new TreeMap<>();
        sortedParas.put("SignatureMethod", "HMAC-SHA1");
        sortedParas.put("SignatureNonce", UUID.randomUUID().toString());
        sortedParas.put("SignatureVersion", "1.0");
        sortedParas.put("Timestamp", formatTimestamp());
        sortedParas.put("Format", "json");
        sortedParas.put("Version", "2017-05-25");
        sortedParas.put("RegionId", "cn-hangzhou");
        sortedParas.putAll(paras);
        sortedParas.remove("Signature");

        String sortedQueryString = buildSortedQueryString(sortedParas);
        String sign = generateSignature(accessSecret, sortedQueryString);
        String encodedSign = encodeForUrl(sign);

        return action + encodedSign + "&" + sortedQueryString;
    }

    /**
     * 格式化当前时间，使其符合阿里云API的时间戳格式要求。
     * 
     * @return 格式化后的时间戳。
     */
    private static String formatTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
        return sdf.format(new Date());
    }

    /**
     * 生成排序后的查询字符串。
     * 
     * @param paras 请求参数。
     * @return 排序后的查询字符串。
     */
    private static String buildSortedQueryString(SortedMap<String, String> paras) {
        StringBuilder queryStringBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : paras.entrySet()) {
            queryStringBuilder.append("&")
                              .append(encodeForUrl(entry.getKey()))
                              .append("=")
                              .append(encodeForUrl(entry.getValue()));
        }
        return queryStringBuilder.substring(1); // 去掉第一个 &
    }

    /**
     * 生成签名。
     * 
     * @param accessSecret 用户的Access Secret。
     * @param stringToSign 待签名的字符串。
     * @return 签名结果。
     */
    private static String generateSignature(String accessSecret, String stringToSign) {
        try {
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(new SecretKeySpec((accessSecret + "&").getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
            byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signData);
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate signature.", e);
        }
    }

    /**
     * 对字符串进行URL编码，同时替换特定字符。
     * 
     * @param value 需要编码的字符串。
     * @return 编码后的字符串。
     */
    private static String encodeForUrl(String value) {
        try {
            return URLEncoder.encode(value, StandardCharsets.UTF_8.toString())
                            .replace("+", "%20")
                            .replace("*", "%2A")
                            .replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Failed to encode value.", e);
        }
    }
}